/*
* Copyright (c) 2011 Connection Road.  All rights reserved.
* This program and the accompanying material are made available
* under the terms of the Eclipse Public Licnese v1.0 which is
* available at http://www.eclipse.org/legal/epl-v10.html
*/

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;

namespace CrSyncFilesUtils {



    /// <summary>
    /// Represents a file or folder on a given folder tree.
    /// Use this class to get information on various files or folders recursively.
    /// 
    /// I did not use "Path" class much because it has issues with "\" symbol and "\\" as the prefix.
    /// </summary>
    public  class FileNode {

        
        /// <summary>
        /// Initialize FileNode object with a given root path.
        /// </summary>
        /// <param name="absolutePath"></param>
        public FileNode(string absolutePath) {
            Path = absolutePath;
            ParentNode = this;
        }

        /// <summary>
        /// Initialize FileNode object with a given root path and a parent node
        /// </summary>
        /// <param name="absolutePath"></param>
        /// <param name="parentNode"></param>
        public FileNode(string absolutePath, FileNode parentNode) {
            Path = absolutePath;
            ParentNode = parentNode;
        }


        public string Path { get; private set; }

        /// <summary>
        /// Gets or sets the parent node
        /// </summary>
        public FileNode ParentNode { get; set; }

        public FileAttributes FileAttribute
        {
            get
            {
                return Exists() ? File.GetAttributes(Path) : FileAttributes.Temporary;
            }
        }
        

        public bool IsDirectory()
        {
            if(Directory.Exists(Path))
            {
                return true;
            }

            return Path.EndsWith("\\");
        }

        /// <summary>
        /// Determine if this current node is a root path.
        /// </summary>
        public bool IsRootPath
        {
          get { return (ParentNode.Path == Path) ? true : false; }
        }

        /// <summary>
        /// Object information that this node represents.
        /// </summary>
        /// <returns>DirectoryInfo object if this current node is a folder or a file if it exists else will return false.</returns>
        public object GetInfo()
        {
            return Directory.Exists(Path)
                       ? (object) new DirectoryInfo(Path)
                       : (File.Exists(Path) ? new FileInfo(Path) : null);
        }


        /// <summary>
        /// The name of the file or directory.
        /// </summary>
        public string GetFileName(bool shouldDetectIfExists = false)
        {
            

                if (IsUnc())
                {

                    if (Path.EndsWith("\\"))
                    {
                        var newPath = Path.Substring(0, Path.Length - 1);
                        var splitedPath1 = newPath.Split('\\');
                        return splitedPath1[splitedPath1.Length - 1];
                    }
                    var splitedPath2 = Path.Split('\\');
                    return splitedPath2[splitedPath2.Length - 1];
                }

                if (IsUrl())
                {
                    if (Path.EndsWith("/"))
                    {
                        var newPath = Path.Substring(0, Path.Length - 1);
                        var splitedPath1 = newPath.Split('/');
                        return splitedPath1[splitedPath1.Length - 1];
                    }

                    var splitedPath2 = Path.Split('/');
                    return splitedPath2[splitedPath2.Length - 1];
                }


                if(shouldDetectIfExists)
                {
                    if (Directory.Exists(Path))
                    {
                        var directoryInfo = (DirectoryInfo)GetInfo();
                        return directoryInfo.Name;
                    }
                }


                if (Path.EndsWith("\\"))
                {
                    var newPath = Path.Substring(0, Path.Length - 1);
                    var splitedDirectoryPath = newPath.Split('\\');
                    return splitedDirectoryPath[splitedDirectoryPath.Length - 1];
                }
               

                // Detecting file name
                if(shouldDetectIfExists)
                {
                    if (File.Exists(Path))
                    {
                        var fileInfo = (FileInfo)GetInfo();
                        return fileInfo.Name;
                    }
                }


                var splitedPath = Path.Split('\\');
                return splitedPath[splitedPath.Length - 1];
                
            
        }

        

        /// <summary>
        /// Retrieves the files and folders from the current path ( the specified path must exists ).
        /// </summary>
        /// <param name="parentNode">Set this to null if the current node is a parent node</param>
        /// <param name="recursive">If set to true, this method will get all files and folders recursively.</param>
        /// <returns></returns>
        public List<FileNode> ChildNodes(FileNode parentNode,bool recursive) {

            var childNodes = new List<FileNode>();

            ParentNode = parentNode ?? this;

            if(IsDirectory()){
                
                //Get all directories first
                var directories = Directory.GetDirectories(Path);
                foreach (var directoryNode in directories.Select(directory => new FileNode(directory)))
                {
                    directoryNode.ParentNode = this;
                    childNodes.Add(directoryNode);
                    if (recursive) {
                        childNodes.AddRange(directoryNode.ChildNodes(directoryNode,true));
                    }
                }

                //Get all files after
                var files = Directory.GetFiles(Path);
                childNodes.AddRange(files.Select(file => new FileNode(file)));
                return childNodes;
            }
            return childNodes;
        }


        /// <summary>
        /// Concatenates path. If the parent path is a regular file, parameter will be ignored.
        /// </summary>
        /// <param name="anAbsolutePath">An absolute path</param>
        public FileNode Join(string anAbsolutePath)
        {
           if (Path.EndsWith("\\"))
           {
                if (anAbsolutePath.StartsWith("\\"))
                {
                     Path = Path + anAbsolutePath.Substring(1);
                 }
                 else
                 {
                     Path = Path + anAbsolutePath;
                 }
            }
            else
            {
                if (anAbsolutePath.StartsWith("\\"))
                {
                   Path = Path + anAbsolutePath;
                }
                else
                {
                   Path = Path + "\\" + anAbsolutePath;
                }
            }

            return this;
        }

        /// <summary>
        /// Check the file/folder existence
        /// </summary>
        /// <returns></returns>
        public bool  Exists()
        {
            return (Directory.Exists(Path) || File.Exists(Path)) ? true : false;
        }


        /// <summary>
        /// Deletes the file completely.
        /// </summary>
        /// <returns></returns>
        /// <exception cref="IOException"></exception>
        public bool Remove()
        {
            if (Directory.Exists(Path))
            {
                Directory.Delete(Path, true);
                return true;
            }

            if (File.Exists(Path))
            {
                File.Delete(Path);
                return true;
            }

            return false;
        }


        /// <summary>
        /// Gets the abosolute path of the parent directory.
        ///  Aeasthic purpose only. The path may or may not exists.
        /// </summary>
        /// <returns></returns>
 
        public string GetParentPath()
        {
            if ((ParentNode.Path == Path) || (ParentNode == null))
            {
                // Designed for windows only
                var parentDirectory = System.IO.Path.GetDirectoryName(Path);
                if (parentDirectory == "\\")
                {
                    return "";
                }
                return parentDirectory;
            }

            return ParentNode.GetParentPath();
        }

        /// <summary>
        /// Gets the relative path from the specified root path
        /// </summary>
        /// <param name="rootPath"></param>
        /// <returns></returns>
        public string GetRelativePathFromRootPath(string rootPath)
        {
           if(Path != rootPath)
            {
                if(Path.StartsWith(rootPath))
                {
                    var relativePath = Path.Substring(rootPath.Length);
                    return relativePath.StartsWith("\\") ? relativePath.Substring(1) : relativePath;
                }
                return Path;
            }
            return Path;
        }

        /// <summary>
        /// Extracts the relative path from the specified rooth path
        /// </summary>
        /// <param name="folderPair"></param>
        /// <returns></returns>
        public string GetRelativePathFromFolderPair(FileSyncPairInfo folderPair)
        {

            var relativePath = GetRelativePathFromRootPath(folderPair.LeftFolder.Path);
            if(relativePath != Path)
            {
                return relativePath;
            }

            relativePath = GetRelativePathFromRootPath(folderPair.RightFolder.Path);
            return relativePath != Path ? relativePath : null;
        }

        
        /// <summary>
        /// Updates the root path of an absolute path.
        /// Use this to update the drive letter, Network root path or the root URL.
        /// </summary>
        /// <param name="driveName"></param>
        /// <returns></returns>
        public FileNode UpdateRootPath(string driveName)
        {
            var split = Path.Split('\\').ToList();
            split.RemoveAt(0);
            var newChildPath = string.Join("\\", split.ToArray());
            Path = System.IO.Path.Combine(driveName, newChildPath);
            return this;
        }

        public string GetRoothPath()
        {
            if(IsUnc())
            {
                return "\\\\";
            }

            if(IsUrl())
            {
                var splitUrl = Path.Split('/');
                return splitUrl[0].ToUpper();
            }


            var splitPath = Path.Split('\\');
            return splitPath[0].ToUpper();

        }

        public bool IsUnc()
        {
            return Path.StartsWith("\\\\");
        }

        public bool IsUrl()
        {
            return Path.Contains("://");
        }



        public bool IsChildOf(string absoluteParentPath)
        {
            return Path.Contains(absoluteParentPath);
        }

        public bool  IsChildOf(FileNode fileNode)
        {
            return IsChildOf(fileNode.Path);
        }


        public int GetDepthForPath(string rootPath)
        {
            var relativePath = GetRelativePathFromRootPath(rootPath);
            if (relativePath == rootPath)
            {
                return 0;
            }
            var split = relativePath.Split('\\');
            return split.Length;
        }

        
    }
}
